package org.moonshot.cvm;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.TreeMap;

import org.moonshot.chatty.parse.MethodScope;

public class ChattyVM
{
	public final ChattyBasic TRUE  = new ChattyBasic (ChattyBasic.Kind.TRUE);
	public final ChattyBasic FALSE = new ChattyBasic (ChattyBasic.Kind.FALSE);
	public final ChattyBasic NIL   = new ChattyBasic (ChattyBasic.Kind.NIL);
	public final ChattyBasic NAN   = new ChattyBasic (ChattyBasic.Kind.NAN);
	
	private final HashMap<Class<?>, I_ChattyClass> mapToClassHandler = new HashMap<Class<?>, I_ChattyClass> ();
	private final TreeMap<String, I_ChattyClass> mapToClass = new TreeMap <String, I_ChattyClass> ();
	
	private ArrayList<File> classpath = new ArrayList<File> ();
	
	/**
	 * Get a class by Java class definition.
	 * @param clazz
	 * @return
	 */
	public I_ChattyClass classFor (Class<? extends I_ChattyClass> clazz)
    {
		I_ChattyClass theClazz = mapToClassHandler.get (clazz);
		if( theClazz == null )
		{
            try
            {
            	theClazz = clazz.newInstance ();
    		    mapToClass.put (clazz.getCanonicalName (), theClazz);
    		    mapToClassHandler.put (clazz, theClazz);
            }
            catch( final Exception e )
            {
    	        e.printStackTrace();
            }
		}
		
		return theClazz;
    }
	
	/**
	 * Get a class by name, dynamically loading it if necessary.
	 * @param aClazz
	 * @return
	 */
	public synchronized I_ChattyClass classNamed (String aClazz)
    {
		if( mapToClass.containsKey (aClazz) )
			return mapToClass.get (aClazz);
	    
		// TODO Look up the class on the classpath.
		for(File theFolder : classpath)
		{
			final File theCheck = new File (theFolder, aClazz+".cvm-class");
			try
            {
    			if( theCheck.isFile () && theCheck.canRead () )
    			{
    				// Load it from file, and make sure it's superclass is loaded as well.
	                CVMClassSpec clazz = new CVMClassSpec (theCheck);
	                clazz.getSuperclass (this);
	                
	                return clazz;
    			}
            }
            catch( Exception e )
            {
            	System.err.println("While loading "+aClazz);
                e.printStackTrace (System.err);
            }
		}
		
		return null;
    }
	
	/**
	 * Remove all dynamically-loaded classes from cache
	 */
	public synchronized void resetClassCache ()
	{
		mapToClass.clear ();
	}
	
	/**
	 * Remove a given dynamically-loaded class from cache
	 * @param aClazz
	 */
	public synchronized void resetClassInCache (String aClazz)
	{
		mapToClass.remove (aClazz);
	}
	
	private static final int EXEC_NONE      = 0x00;
	private static final int EXEC_SELFVALID = 0x01;
	private static final int EXEC_NEWVALID  = 0x02;
	private static final int EXEC_CREATE    = 0x10;
	private static final int EXEC_ISBLOCK   = 0x20;
	
	private int execFlagsFor (MethodScope scope)
    {
		switch(scope)
		{
		case CONSTRUCTOR:
			return EXEC_CREATE | EXEC_SELFVALID | EXEC_NEWVALID;
			
		case INSTANCE:
			return EXEC_SELFVALID;
			
		case CLASS:
			return EXEC_NONE;
		
		default:
			return EXEC_NONE;
		}
    }
	
	private int execFlagsForBlock ()
	{
		return EXEC_SELFVALID | EXEC_ISBLOCK;
	}
	
	public Object callChatty(Object aInstance, String aMethod, Object... aParams)
		throws CVMRuntimeException, CVMBlockReturnException
	{
		final ChattyStack stack = new ChattyStack ();
		for(Object aParam: aParams)
			stack.push (aParam);
		
		return callChatty (aInstance, aMethod, stack, aParams.length);
	}
	
	public Object callChatty(Object aInstance, String aMethod, ChattyStack aStack, int nParams)
		throws CVMRuntimeException, CVMBlockReturnException
	{
		I_ChattyClass clazz;
		if( aInstance == null )
			clazz = NIL;
		else
		{
			final Class<?> aClass = aInstance.getClass ();
			clazz = mapToClassHandler.get (aClass);
			if( clazz == null )
			{
				if( aInstance instanceof I_ChattyCompatible )
					clazz = ((I_ChattyCompatible)aInstance).identifySelfClass (this);
				else
				{
    				clazz = mapToClass.get (aClass.getCanonicalName ());
    				if( clazz == null )
    				{
    					throw new CVMRuntimeException ("No class mapping for "+aClass.getCanonicalName ());
    				}
				}
			}
		}
		
		return clazz.callInstance (this, aInstance, aMethod.intern (), aStack, nParams);
	}
	
	public CVMRuntimeException throwMethodDoesNotExist ()
    {
	    return new CVMRuntimeException ("Method not defined for this type");
    }
	
	/**
	 * Ensure this CVM is ready to use the given object; nearly always returns the object directly.
	 * @param o
	 * @return
	 */
	public Object convertObject (Object o)
	{
		if( o == null ) return NIL;
		if( o instanceof Number ) return convertObject (((Number)o).doubleValue ());
		
		// TODO Check for a known surrogate/interface class. Return object direct.
		// TODO Check for a known converter/interface class. Return converted form.
		// TODO Check for I_ChattyCompatible. Return object direct.
		// TODO Try to automatically create a surrogate/interface. Return object direct.
		
		// TODO Log an error, before returning a CVMUnknown
		return new CVMUnknown(o);
	}
	
	public Object convertObject (boolean b)
    {
	    return b ? TRUE : FALSE;
    }
	
	public Object convertObject (int i)
	{
		return new ChattyNumber(i);
	}
	
	public Object convertObject (double d)
	{
		if( Double.isNaN (d) )
			return NAN;
		
		return new ChattyNumber(d);
	}
	
	public Object execute (Object aSelf, int aFlags, CVMBinary aSource, int aPC, ChattyStack aContext)
		throws CVMRuntimeException, CVMBlockReturnException
    {
		
	    return null;
    }
	
	public Object execute (Object aSelf, CVMMethod aMethod, ChattyStack aStack)
		throws CVMRuntimeException, CVMBlockReturnException
	{
		if( aMethod.usesFullBlock )
		{
			final CVMContext context = new CVMContext (this, aMethod.params + aMethod.temps, aStack, aMethod.params);
			try
			{
				return execute(aSelf, execFlagsFor(aMethod.scope), aMethod.source, aMethod.pcInit, context);
			}
			catch (CVMBlockReturnException e)
			{
				if( e.context == context )
				{
					return e.value;
				}
			}
			
			throw new CVMRuntimeException ("Method neither returned a value, nor threw a block return");
		}
		else
		{
			return execute(aSelf, execFlagsFor(aMethod.scope), aMethod.source, aMethod.pcInit, aStack);
		}
	}
	
	/**
	 * Execute a block, taking as its injections the top <code>nParams</code> elements from <code>aStack</code>.
	 * @param aBlock
	 * @param aStack
	 * @param nParams
	 * @return
	 * @throws CVMRuntimeException
	 * @throws CVMBlockReturnException
	 */
	public Object execute (CVMBlock aBlock, ChattyStack aStack, int nParams)
		throws CVMRuntimeException, CVMBlockReturnException
	{
		final CVMContext context = aBlock.context;
		if( context == null )
			throw new CVMRuntimeException ("Executing uninitialized block");
		
		int i = 0, idx = aBlock.firstParam;
		while( i < nParams && i < aBlock.nParams )
			context.put (idx++, aStack.at (nParams - i++));
		while( idx < aBlock.nParams )
			context.put (idx++, NIL);
		
		return execute(aBlock.self, execFlagsForBlock (), aBlock.source, aBlock.pcInit, context);
	}
	
	/**
	 * Execute a block, injecting values from the given array.
	 * @param aBlock
	 * @param arr
	 * @return
	 * @throws CVMRuntimeException
	 * @throws CVMBlockReturnException
	 */
	public Object execute (CVMBlock aBlock, Object[] arr)
		throws CVMRuntimeException, CVMBlockReturnException
    {
		final CVMContext context = aBlock.context;
		if( context == null )
			throw new CVMRuntimeException ("Executing uninitialized block");
		
		int i = 0, idx = aBlock.firstParam;
		while( i < arr.length && i < aBlock.nParams )
			context.put (idx++, arr[i++]);
		while( idx < aBlock.nParams )
			context.put (idx++, NIL);
		
		return execute(aBlock.self, execFlagsForBlock (), aBlock.source, aBlock.pcInit, context);
    }
	
	/**
	 * 
	 */
}
